﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace Microsoft.AspNetCore.Mvc.ApplicationModels;

/// <summary>
/// An <see cref="IActionModelConvention"/> that adds a <see cref="ConsumesAttribute"/> with <c>multipart/form-data</c>
/// to controllers containing form file (<see cref="BindingSource.FormFile"/>) parameters.
/// </summary>
public class ConsumesConstraintForFormFileParameterConvention : IActionModelConvention
{
    /// <inheritdoc />
    public void Apply(ActionModel action)
    {
        ArgumentNullException.ThrowIfNull(action);

        if (!ShouldApply(action))
        {
            return;
        }

        AddMultipartFormDataConsumesAttribute(action);
    }

    /// <summary>
    /// Determines if this instance of <see cref="IActionModelConvention"/> applies to a specified <paramref name="action"/>.
    /// </summary>
    /// <param name="action">The <see cref="ActionModel"/>.</param>
    /// <returns>
    /// <see langword="true"/> if the convention applies, otherwise <see langword="false"/>.
    /// Derived types may override this method to selectively apply this convention.
    /// </returns>
    protected virtual bool ShouldApply(ActionModel action) => true;

    // Internal for unit testing
    internal static void AddMultipartFormDataConsumesAttribute(ActionModel action)
    {
        // Add a ConsumesAttribute if the request does not explicitly specify one.
        if (action.Filters.OfType<IConsumesActionConstraint>().Any())
        {
            return;
        }

        foreach (var parameter in action.Parameters)
        {
            var bindingSource = parameter.BindingInfo?.BindingSource;
            if (bindingSource == BindingSource.FormFile)
            {
                // If an controller accepts files, it must accept multipart/form-data.
                action.Filters.Add(new ConsumesAttribute("multipart/form-data"));
                return;
            }
        }
    }
}
